﻿using NHibernate;
using NHibernate.Linq;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Transactions;
using Configuration = NHibernate.Cfg.Configuration;

namespace Lhz.Data.Linq.Nh
{
    public class NhProxy : LinqProxy
    {
        private static Dictionary<string, ISessionFactory> cacheSessionFactories;
        private static Dictionary<string, Configuration> cacheConfigurations;
        private ISessionFactory sessionFactory;
        private Configuration configuration;
        private static ISession transactionSession;
        private ISession session;

        public NhProxy(string pathConfiguration = null)
        {
            if (cacheSessionFactories == null)
            {
                cacheSessionFactories = new Dictionary<string, ISessionFactory>();
            }
            if (cacheConfigurations == null)
            {
                cacheConfigurations = new Dictionary<string, Configuration>();
            }
            if (string.IsNullOrWhiteSpace(pathConfiguration))
            {
                pathConfiguration = ConfigurationManager.AppSettings["NHibernateConfigurationPath"];
                if (string.IsNullOrWhiteSpace(pathConfiguration))
                {
                    pathConfiguration = string.Empty;
                }
                else
                {
                    if (!Path.IsPathRooted(pathConfiguration))
                    {
                        pathConfiguration = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, pathConfiguration);
                    }
                }
            }
            if (cacheConfigurations.ContainsKey(pathConfiguration))
            {
                configuration = cacheConfigurations[pathConfiguration];
            }
            else
            {
                configuration = new Configuration();
                if (string.IsNullOrWhiteSpace(pathConfiguration))
                {
                    configuration = configuration.Configure();
                }
                else
                {
                    configuration = configuration.Configure(pathConfiguration);
                }
                cacheConfigurations.Add(pathConfiguration, configuration);
            }
            if (cacheSessionFactories.ContainsKey(pathConfiguration))
            {
                sessionFactory = cacheSessionFactories[pathConfiguration];
            }
            else
            {
                sessionFactory = configuration.BuildSessionFactory();
                cacheSessionFactories.Add(pathConfiguration, sessionFactory);
            }
            BuildSession();
        }

        private void BuildSession()
        {
            if (Transaction.Current == null)
            {
                session = sessionFactory.OpenSession();
            }
            else
            {
                if (transactionSession == null)
                {
                    transactionSession = sessionFactory.OpenSession();
                    Transaction.Current.TransactionCompleted += (sender, e) =>
                    {
                        transactionSession = null;
                    };
                }
                session = transactionSession;
            }
        }

        public override IQueryable<TEntity> Query<TEntity>()
        {
            return session.Query<TEntity>();
        }

        protected override string[] GetPkColumns<TEntity>()
        {
            var pc = configuration.GetClassMapping(typeof(TEntity));
            if (pc.Table.HasPrimaryKey)
            {
                return pc.Table.PrimaryKey.Columns.Select(r => r.Name).ToArray();
            }
            else
            {
                return new string[0];
            }
        }

        public override void ClearCache()
        {
            session.Clear();
        }

        public override void Add<TEntity>(TEntity item)
        {
            session.Save(item);
        }

        public override void Update<TEntity>(TEntity item)
        {
            session.Update(session.Merge(item));
        }

        public override void Delete<TEntity>(TEntity item)
        {
            session.Delete(session.Merge(item));
        }

        public override void DeleteByPk<TPk, TEntity>(TPk pk)
        {
            var item = QueryByPk<TPk, TEntity>(pk);
            session.Delete(item);
        }

        public override void SaveChanges()
        {
            session.Flush();
        }

        public override void Dispose()
        {
            session.Dispose();
        }
    }
}
